\section{shell工具和脚本}
\subsection{\texttt{ls}命令的使用}
\begin{questionbox}
阅读 man ls ，然后使用 ls 命令进行如下操作：

所有文件（包括隐藏文件）\\
文件打印以人类可以理解的格式输出 (例如，使用 454M 而不是 454279954)\\
文件以最近修改顺序排序\\
以彩色文本显示输出结果
\end{questionbox}

首先我们使用\texttt{man ls}指令来查看\texttt{ls}命令的使用手册，结果如图\ref{ls的使用手册}

\begin{figure}[htb]
	\centering
	\includegraphics[width=\linewidth]{assets/images/屏幕截图 2025-09-07 103134.png}
	\caption{ls的使用手册}
	\label{ls的使用手册}
\end{figure}

在手册中我们了解到如下内容：\\
\noindent
\begin{minipage}{\textwidth}
\begin{itemize}
    \item \texttt{-a}：显示所有文件，包括隐藏文件
    \item \texttt{-l}：长格式显示（权限、所有者、大小、时间、文件名）
    \item \texttt{-h}：人类可读的大小格式（K、M、G）
    \item \texttt{-t}：按修改时间排序，最新的在前
    \item \texttt{--color=auto}：启用彩色输出（不同文件类型不同颜色）
\end{itemize}
\captionof{figure}{ls 命令常用选项说明}
\label{lst:ls-options}
\end{minipage}

具体操作如图\ref{ls具体操作}：
\begin{figure}[htb]
	\centering
	\includegraphics[width=\linewidth]{assets/images/屏幕截图 2025-09-07 103855.png}
	\caption{ls具体操作}
	\label{ls具体操作}
\end{figure}

我们首先分别使用图\ref{lst:ls-options}中的命令，然后多个命令组合使用，实验结果符合我们的预期。

\subsection{编写bash函数}
\begin{questionbox}
    编写两个 bash 函数 marco 和 polo 执行下面的操作。 每当你执行 marco 时，当前的工作目录应当以某种形式保存，当执行 polo 时，无论现在处在什么目录下，都应当 cd 回到当时执行 marco 的目录。 为了方便 debug，你可以把代码写在单独的文件 marco.sh 中，并通过 source marco.sh 命令，（重新）加载函数。
\end{questionbox}

为了解决这个问题，我们可以将下面的代码写入\texttt{marco.sh}文件中。
\begin{lstlisting}
#!/bin/bash
marco() {
    local MARKER_FILE="$HOME/.marco_location"
    echo "$PWD" > "$MARKER_FILE"
    if [ $? -eq 0 ]; then
        echo "marco: 位置已保存 -> $PWD"
    else
        echo "错误：无法保存位置到 $MARKER_FILE"
        return 1
    fi
}

polo() {
    local MARKER_FILE="$HOME/.marco_location"
    if [ ! -f "$MARKER_FILE" ]; then
        echo "错误：未找到保存的位置（文件 $MARKER_FILE 不存在）"
        echo "请先执行 marco 保存位置"
        return 1
    fi

    local SAVED_DIR=$(cat "$MARKER_FILE")
    if [ -d "$SAVED_DIR" ]; then
        cd "$SAVED_DIR"
        echo "polo: 已跳转到 -> $SAVED_DIR"
    else
        echo "错误：保存的目录不存在或已删除"
        echo "路径: $SAVED_DIR"
        return 1
    fi
}
\end{lstlisting}

过程如图\ref{编写bash函数},我们首先使用\texttt{vim marco.sh}命令创建并修改\texttt{marco.sh}文件，然后将上文中的代码写入\texttt{marco.sh}文件中，最后\texttt{Esc}→\texttt{:wq}保存退出。然后输入\texttt{. marco.sh}加载函数。函数使用示例如下：
\begin{lstlisting}
# 进入某个目录并保存位置
cd /tmp
marco
# 输出：marco: 位置已保存 -> /tmp

# 切换到其他目录
cd ~/Documents

# 执行 polo 回到之前的位置
polo
# 输出：polo: 已跳转到 -> /tmp
\end{lstlisting}

\begin{figure}[htb]
	\centering
	\includegraphics[width=\linewidth]{assets/images/屏幕截图 2025-09-07 105820.png}
	\caption{编写bash函数}
	\label{编写bash函数}
\end{figure}

\subsection{编写脚本和调试}
\begin{questionbox}
    假设您有一个命令，它很少出错。因此为了在出错时能够对其进行调试，需要花费大量的时间重现错误并捕获输出。 编写一段 bash 脚本，运行如下的脚本直到它出错，将它的标准输出和标准错误流记录到文件，并在最后输出所有内容。 加分项：报告脚本在失败前共运行了多少次。
    \begin{lstlisting}
#!/usr/bin/env bash

 n=$(( RANDOM % 100 ))

 if [[ n -eq 42 ]]; then
    echo "Something went wrong"
    >&2 echo "The error was using magic numbers"
    exit 1
 fi

 echo "Everything went according to plan"
    \end{lstlisting}
\end{questionbox}

为了解决这个问题，我们可以将如下代码写入脚本:
\begin{lstlisting}
#!/usr/bin/env bash

# 定义日志文件路径
LOG_FILE="command_output.log"
COUNT=0

# 清空日志文件（如果存在）
> "$LOG_FILE"

# 给定的命令
COMMAND='
n=$(( RANDOM % 100 ))

if [[ n -eq 42 ]]; then
    echo "Something went wrong"
    >&2 echo "The error was using magic numbers"
    exit 1
fi

echo "Everything went according to plan"
'

# 运行命令直到出错
while true; do
    ((COUNT++))
    # 使用 eval 执行命令，并捕获标准输出和标准错误流
    if ! eval "$COMMAND" >> "$LOG_FILE" 2>&1; then
        break
    fi
done

# 输出总运行次数和日志内容
echo "Total runs before failure: $COUNT"
cat "$LOG_FILE"

\end{lstlisting}

我们将上面的代码保存为\texttt{run\_error.sh}，然后输入如下命令运行：
\begin{lstlisting}
    chmod +x run_error.sh #给\texttt{run_error.sh}执行的权限
    ./run_error.sh
\end{lstlisting}

具体结果如图\ref{编写脚本和调试}.我们按照上文中的命令运行后，再次通过\texttt{ls}命令查看当前目录下的文件，发现多出了一个\texttt{command\_output.log}的日志文件，然后通过\texttt{cat command\_output.log}查看该日志文件，发现我们的日志成功写入。

\begin{figure}[H]
	\centering
	\includegraphics[width=\linewidth]{assets/images/屏幕截图 2025-09-07 153836.png}
	\caption{编写脚本和调试}
	\label{编写脚本和调试}
\end{figure}

\subsection{\texttt{find}命令的使用}
\begin{questionbox}
    您的任务是编写一个命令，它可以递归地查找文件夹中所有的 HTML 文件，并将它们压缩成 zip 文件。注意，即使文件名中包含空格，您的命令也应该能够正确执行
\end{questionbox}

为了解决这个问题，我们使用如下的命令：
\begin{lstlisting}
find . -name "*.html" -type f -print0 | xargs -0 zip html-files.zip
\end{lstlisting}
这段命令的含义如下：
\begin{itemize}
    \item \texttt{find . -name "*.html" -type f}:从当前目录递归查找所有\texttt{.html}文件
    \item \texttt{-print0}:使用 空字符（null）分隔文件名（而不是换行或空格）
    \item \texttt{ xargs -0}:告诉\texttt{xargs}输入是以空字符分隔的（与\texttt{-print0}配对）
    \item \texttt{zip html-files.zip}:将所有文件添加到\texttt{html-files.zip}
\end{itemize}

具体操作如下\ref{find命令使用}
\begin{figure}[htb]
	\centering
	\includegraphics[width=\linewidth]{assets/images/屏幕截图 2025-09-07 155650.png}
	\caption{find命令使用}
	\label{find命令使用}
\end{figure}

由于最开始目录中是没有HTML文件的，于是我们使用\texttt{touch}命令创建了三个HTML文件（\texttt{page1.html,page2.html,page3.html}）。然后输入上面的命令，出现了如下问题：
\begin{lstlisting}
caochun@CAOCHUN:~$ find . -name "*.html" -type f -print0 | xargs -0 zip html-files.zip
xargs: zip: No such file or directory
\end{lstlisting}

这是因为我们的子系统中没有安装\texttt{zip}命令导致的，我们这个时候就需要安装我们的\texttt{zip}命令了，输入如下命令来安装\texttt{zip}命令：
\begin{lstlisting}
    sudo apt update
    sudo apt install zip
\end{lstlisting}

接着再次输入刚才的命令。通过\texttt{ls}命令我们观察到目录中多出了一个\texttt{html-files.zip}文件，然后通过\texttt{unzip html-files.zip}来查看压缩包中的文件，发现正是我们新建的三个HTML文件，符合我们的预期。

\subsection{进阶：递归查找文件}
\begin{questionbox}
    （进阶）编写一个命令或脚本递归的查找文件夹中最近修改的文件。更通用的做法，你可以按照最近的修改时间列出文件吗？
\end{questionbox}

为了解决这个问题，我们可以使用 find、stat、sort 和 awk 等命令组合来实现。具体命令如下：
\begin{lstlisting}
    find . -type f -print0 | xargs -0 stat -c '%Y %n' | sort -nr | cut -d' ' -f2- | head -n 20
\end{lstlisting}

在这段命令中：
\begin{itemize}
    \item \texttt{find . -type f -print0}:递归查找所有文件，用 null 分隔（防空格错误）
    \item \texttt{xargs -0 stat -c '\%Y \%n'}:获取每个文件的「修改时间戳 + 文件名」
    \item \texttt{sort -nr}:按时间戳数字逆序排序（最新在前）
    \item \texttt{cut -d' ' -f2-}:去掉时间戳，只保留文件名（处理空格）
    \item \texttt{head -n 20}:只显示最新的 20 个（可删掉看全部,也可以改变显示的个数）
\end{itemize}

具体操作如图\ref{进阶：递归查找文件}：
\begin{figure}[htb]
	\centering
	\includegraphics[width=\linewidth]{assets/images/屏幕截图 2025-09-07 161749.png}
	\caption{进阶：递归查找文件}
	\label{进阶：递归查找文件}
\end{figure}